/**
 * Copyright (c) 2017, biezhi 王爵 (biezhi.me@gmail.com)
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.frank.demo.nio.netty.http;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpServerExpectContinueHandler;
import io.netty.handler.codec.http.cors.CorsConfig;
import io.netty.handler.codec.http.cors.CorsConfigBuilder;
import io.netty.handler.codec.http.cors.CorsHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.ResourceLeakDetector;
import lombok.extern.slf4j.Slf4j;
import sun.nio.ch.Net;

/**
 * Netty Web Server
 *
 * @author biezhi
 *         2017/5/31
 */
@Slf4j
public class NettyServer implements Server {

    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private Channel channel;
    static final boolean SSL = System.getProperty("ssl") != null;


    public static void main(String[] args) throws Exception {
        new NettyServer().start(args);
    }
    /**
     * 启动netty服务器
     *
     * @param args command arguments
     * @throws Exception
     */
    @Override
    public void start(String[] args) throws Exception {
        this.startServer(System.currentTimeMillis());
    }


    private void startServer(long startTime) throws Exception {

        ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED);//netty 不进行内存泄漏检测

        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey());
        } else {
            sslCtx = null;
        }

        // Configure the server.
        ServerBootstrap b = new ServerBootstrap();
        b.option(ChannelOption.SO_BACKLOG, 8192);//请求队列长度
        b.option(ChannelOption.SO_REUSEADDR, true);//允许重复使用本地地址和端口
        b.childOption(ChannelOption.SO_REUSEADDR, true);//ServerChannel 的子channel 允许重复使用本地地址和端口

        b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);//netty 内存池管理
        b.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);//关键是这句

        int acceptThreadCount = 1;//boss使用的线程数
        int ioThreadCount = 0;//worker使用的线程数

        //ioThreadCount 为0时,默认线程数量为 cpu核心数的2倍 也可以通过-Dio.netty.eventLoopThreads 指定
        this.bossGroup = new NioEventLoopGroup(acceptThreadCount, new NamedThreadFactory("boss@"));
        this.workerGroup = new NioEventLoopGroup(ioThreadCount, new NamedThreadFactory("worker@"));
        b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class);

        b.handler(new LoggingHandler(LogLevel.DEBUG))
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline p = ch.pipeline();
                        if (sslCtx != null) {
                            p.addLast(sslCtx.newHandler(ch.alloc()));
                        }
                        //启动gzip
                        if (true) {
                            p.addLast(new HttpContentCompressor());
                        }
                        //http服务器解码器
                        p.addLast(new HttpServerCodec(36192 * 2, 36192 * 8, 36192 * 16, false));
                        //http 100-continue用于客户端在发送POST数据给服务器前，征询服务器情况，看服务器是否处理POST的数据，如果不处理，客户端则不上传POST数据，如果处理，则POST上传数据。在现实应用中，通过在POST大数据时，才会使用100-continue协议。
                        p.addLast(new HttpServerExpectContinueHandler());
                        //它负责把多个HttpMessage组装成一个完整的Http请求或者响应
                        p.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
                        //增加了对异步写入大量数据流的支持
                        p.addLast(new ChunkedWriteHandler());
                        if (true) {
                            CorsConfig corsConfig = CorsConfigBuilder.forAnyOrigin().allowNullOrigin().allowCredentials().build();
                            //处理跨源资源共享（CORS）请求。
                            p.addLast(new CorsHandler(corsConfig));
                        }

//                        if (null != blade.webSocketPath()) {
//                            //websocket 基础功能
//                            p.addLast(new WebSocketServerProtocolHandler(blade.webSocketPath(), null, true));
//                            p.addLast(new WebSocketHandler(blade));
//                        }

                        p.addLast(new HttpServerHandler());
                    }
                });

        String address = "0.0.0.0";
        int port = 9000;

        channel = b.bind(address, port).sync().channel();
    }

    @Override
    public void stop() {
        log.info("{}Blade shutdown ...", getStartedSymbol());
        try {
            if (this.bossGroup != null) {
                this.bossGroup.shutdownGracefully();
            }
            if (this.workerGroup != null) {
                this.workerGroup.shutdownGracefully();
            }
            log.info("{}Blade shutdown successful", getStartedSymbol());
        } catch (Exception e) {
            log.error("Blade shutdown error", e);
        }
    }

    @Override
    public void stopAndWait() {
        log.info("{}Blade shutdown ...", getStartedSymbol());
        try {
            if (this.bossGroup != null) {
                this.bossGroup.shutdownGracefully().sync();
            }
            if (this.workerGroup != null) {
                this.workerGroup.shutdownGracefully().sync();
            }
            log.info("{}Blade shutdown successful", getStartedSymbol());
        } catch (Exception e) {
            log.error("Blade shutdown error", e);
        }
    }

    @Override
    public void join() throws InterruptedException {
        channel.closeFuture().sync();
    }

    private String getStartedSymbol() {
        return "»";
    }
}
